<template>
    <div class="neuro-container">
        <canvas class="neuro-content" :width="width" :height="height"></canvas>
    </div>
</template>

<script>

export default {
    name: "Neuro",
    data() {
        return {
            width: 200,
            height: 200,
            pointer: {
                x: 0,
                y: 0,
                tX: 0,
                tY: 0,
            },
            devicePixelRatio: 1,
            uniforms: null,
            canvas: null,
            gl: null,
        }
    },
    methods: {

        createShader(gl, sourceCode, type) {
            const shader = gl.createShader(type);
            gl.shaderSource(shader, sourceCode);
            gl.compileShader(shader);

            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                console.error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
                return null;
            }

            return shader;
        },
        createShaderProgram(gl, vertexShader, fragmentShader) {
            const program = gl.createProgram();
            gl.attachShader(program, vertexShader);
            gl.attachShader(program, fragmentShader);
            gl.linkProgram(program);

            if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
                console.error("Unable to initialize the shader program: " + gl.getProgramInfoLog(program));
                return null;
            }

            return program;
        },
        getUniforms(program) {
            this.uniforms = [];
            let uniformCount = this.gl.getProgramParameter(program, this.gl.ACTIVE_UNIFORMS);
            for (let i = 0; i < uniformCount; i++) {
                let uniformName = this.gl.getActiveUniform(program, i).name;
                this.uniforms[uniformName] = this.gl.getUniformLocation(program, uniformName);
            }
        },

        initShader() {
            const vsSource = `
        precision mediump float;

    varying vec2 vUv;
    attribute vec2 a_position;

void main() {
    vUv = .5 * (a_position + 1.);
    gl_Position = vec4(a_position, 0.0, 1.0);
}
    `;
            const fsSource = `
    precision mediump float;

    varying vec2 vUv;
    uniform float u_time;
    uniform float u_ratio;
    uniform vec2 u_pointer_position;
    uniform float u_scroll_progress;

    vec2 rotate(vec2 uv, float th) {
        return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv;
    }

    float neuro_shape(vec2 uv, float t, float p) {
        vec2 sine_acc = vec2(0.);
        vec2 res = vec2(0.);
        float scale = 8.;

        for (int j = 0; j < 15; j++) {
            uv = rotate(uv, 1.);
            sine_acc = rotate(sine_acc, 1.);
            vec2 layer = uv * scale + float(j) + sine_acc - t;
            sine_acc += sin(layer);
            res += (.5 + .5 * cos(layer)) / scale;
            scale *= (1.2 - .07 * p);
        }
        return res.x + res.y;
    }

    void main() {
        vec2 uv = .5 * vUv;
        uv.x *= u_ratio;

        vec2 pointer = vUv - u_pointer_position;
        pointer.x *= u_ratio;
        float p = clamp(length(pointer), 0., 1.);
        p = .5 * pow(1. - p, 2.);

        float t = .001 * u_time;
        vec3 color = vec3(0.);

        float noise = neuro_shape(uv, t, p);

        noise = 1.2 * pow(noise, 3.);
        noise += pow(noise, 10.);
        noise = max(.0, noise - .5);
        noise *= (1. - length(vUv - .5));

        color = normalize(vec3(.2, .5 + .4 * cos(3. * u_scroll_progress), .5 + .5 * sin(3. * u_scroll_progress)));

        color = color * noise;

        gl_FragColor = vec4(color, noise);
    }
    `;

            this.gl = this.canvas.getContext("webgl") || this.canvas.getContext("experimental-webgl");

            if (!this.gl) {
                return null;
            }



            const vertexShader = this.createShader(this.gl, vsSource, this.gl.VERTEX_SHADER);
            const fragmentShader = this.createShader(this.gl, fsSource, this.gl.FRAGMENT_SHADER);



            const shaderProgram = this.createShaderProgram(this.gl, vertexShader, fragmentShader);
            this.getUniforms(shaderProgram);



            const vertices = new Float32Array([-1., -1., 1., -1., -1., 1., 1., 1.]);

            const vertexBuffer = this.gl.createBuffer();
            this.gl.bindBuffer(this.gl.ARRAY_BUFFER, vertexBuffer);
            this.gl.bufferData(this.gl.ARRAY_BUFFER, vertices, this.gl.STATIC_DRAW);

            this.gl.useProgram(shaderProgram);

            const positionLocation = this.gl.getAttribLocation(shaderProgram, "a_position");
            this.gl.enableVertexAttribArray(positionLocation);

            this.gl.bindBuffer(this.gl.ARRAY_BUFFER, vertexBuffer);
            this.gl.vertexAttribPointer(positionLocation, 2, this.gl.FLOAT, false, 0, 0);
        },
        updateMousePosition(event) {
            let eX = event.clientX;
            let eY = event.clientY;
            this.pointer.tX = eX;
            this.pointer.tY = eY;
        },

        render() {
            const currentTime = performance.now();

            this.pointer.x += (this.pointer.tX - this.pointer.x) * .5;
            this.pointer.y += (this.pointer.tY - this.pointer.y) * .5;

            this.gl.uniform1f(this.uniforms.u_time, currentTime);
            this.gl.uniform2f(this.uniforms.u_pointer_position, this.pointer.x / this.width, 1 - this.pointer.y / this.height);
            this.gl.uniform1f(this.uniforms.u_scroll_progress, 0.5);

            this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
            requestAnimationFrame(this.render.bind(this));
        },

        resizeCanvas() {
            this.width = window.innerWidth * this.devicePixelRatio;
            this.height = window.innerHeight * this.devicePixelRatio;
            this.gl.uniform1f(this.uniforms.u_ratio, this.width / this.height);
            this.gl.viewport(0, 0, this.width, this.height);
        },
    },
    mounted() {
        this.canvas = this.$el.querySelector(".neuro-content");
        this.devicePixelRatio = Math.min(window.devicePixelRatio, 2);

        this.initShader();
        if (this.gl) {
            this.resizeCanvas();
            this.render();

            this.canvas.addEventListener("mousemove", this.updateMousePosition);
            this.canvas.addEventListener("resize", this.resizeCanvas);
        }
    },
    beforeUnmount() {
        if (this.canvas) {
            this.canvas.removeEventListener("mousemove", this.updateMousePosition);
            this.canvas.removeEventListener("resize", this.resizeCanvas);
        }
    },
}
</script>

<style scoped>
.neuro-container {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;
}

.neuro-content {
    width: 100%;
    height: 100%;
    opacity: .95;
}
</style>
